<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS里的Object.defineProperty</title>
    <style>
        a {
            display: block;
            width: 200px;
            height: 40px;
            text-align: center;
            line-height: 40px;
            background-color: lightseagreen;
            color: #fff;
            border-radius: 8px;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <h3>
        要理解vue中的数据代理和数据劫持，必须先了解js中的Object.defineProperty方法。
    </h3>
    <h3>
        Object.defineProperty方法，是为对象数据进行一系列操作的方法。
    </h3>
    <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty">跳转到MDN查看详细介绍</a>
    <script>
        // 给出一个普通对象person
        let person = {
            name: 'dilireba',
        }
        // 给person对象添加一个age属性，值为18
        // 一般做法
        person.age = 18;

        console.log(person);
        // 此时person发生了变化：
        // person = {name: 'dilireba', age: 18}

        /********************************************/

        // 用Object.defineProperty给person对象增加一个属性sex，值为0
        Object.defineProperty(person, 'sex', {
            value: 0
        } )

        console.log(person);
        // 此时person发生了变化：
        // person = {name: "dilireba", age: 18, sex: 0}

        // Object.defineProperty有三个参数
        // 第一个参数是一个对象,如person， 表示要给该对象添加属性了
        // 第二个参数是一个字符串属性，表示要对这个属性进行某些操作了。
        // 第三个参数，是一个对象类型的配置项。这个配置项里有多个选项可以进行配置。
        // 上面的例子中使用了value选项的配置。给person添加了一个属性sex，并赋值为0

        /********************************************/
        // 对person对象进行遍历
        console.log(Object.keys(person));

        // 得到的枚举结果是["name", "age"]

        // 此时可以发现，有个属性没有被遍历到，即通过defineProperty添加的sex属性

        // 这就是涉及到另一个配置选项。enumerable

        // 给对象添加属性时，设置enumerable为true，则表示经defineProperty添加的这个属性，是可以被遍历到的。 默认是false.

        Object.defineProperty(person, 'hobby', {
            value: 'sing',
            enumerable: true,
        })

        // 此时再遍历person对象：
        console.log(Object.keys(person));
        // 遍历结果是 ["name", "age", "hobby"]
        // 可以看到，刚才添加的hobby属性，就可以被遍历到了。

        /********************************************/
        // 现在来试试删除一个属性
        console.log(delete person.age); // true， 说明age属性删除成功
        console.log(delete person.hobby); // false,说明hobby属性没有被删除

        // 这是可以发现，person自身携带的属性，可以直接删除。而通过defineProperty添加的属性，不能删除
        // 如果想删除hobby属性，可以给hobby添加一个选项配置 configurable
        // configurable表示该属性是否能被删除。默认为false，表示不能删除。设置为true，表示可以删除。

        Object.defineProperty(person, 'love', {
            value: 'film',
            configurable: true,
        })

        console.log(delete person.love); // true，表示刚才添加的love属性被删除了。

        /********************************************/
        // 修改一个属性的值
        // 普通方法的修改：
        person.name = 'dili';
        console.log(person); // {name: "dili", hobby: "sing", sex: 0}
        // 可以发现，name被修改了

        // 再修改一下hobby:
        person.hobby = 'sleep';
        console.log(person); // {name: "dili", hobby: "sing", sex: 0}
        // 可以看出，hobby并没有被修改掉。
        // 因为通过defineProperty添加的属性，不能直接被修改。如果需要修改，必须在选项中进行配置 writable
        // writable默认为false，表示不可被修改。 设置为true，表示可以被修改。

        Object.defineProperty(person, 'age', {
            value: 18,
            writable: true
        })
        console.log(person); // {name: "dili", hobby: "sing", sex: 0, age: 18}

        person.age = 19;
        console.log(person); // {name: "dili", hobby: "sing", sex: 0, age: 19}
        // 可以看出，此时，就可以修改被添加的属性的值了。

        /********************************************/
        // 此时，给定一个值：
        let num = 10;

        // 给定一个对象person1,并且让age属性的值取自于num
        let person1 = {
            age: num
        }
        console.log(person1); // {age: 10}

        // 修改num的值为20
        num = 20;
        console.log(person1); // {age: 10}
        // 可以看到，num的值修改了，但是person中的age的值并没有跟着变化
        // 怎么才能做到 只要num发生变化，age就跟着变化呢？
        // 可以考虑用一个函数，每次要获取age的值时，都调用这个函数去获取num的值
        // defineProperty中有一个get函数，用来获取最新的值
        delete person1.age;
        Object.defineProperty(person1, 'age', {
            configurable: true,
            get() {
                console.log('有人读取age属性了，get被调用了');
                return num;
            }
        })
        console.log(person1);

        // 修改num的值
        num = 30;
        console.log(person1);
        // 可以看到age的值随着num的变化而变化。
        // get函数又叫getter。每次获取age的值时，都会被调用，getter返回的值，就是age的值。

        /********************************************/
        // 和getter同样的，也有一个函数体setter，当age发生改变时，会调用setter
        Object.defineProperty(person1, 'age', {
            configurable: true,
            get() {
                console.log('有人读取age属性了，get被调用了');
                return num;
            },
            set(value) {
                console.log('有人修改age属性了，set被调用了, 修改后的值是: ', value);
            }
        })

        // 修改age
        person1.age = 40;
        console.log(person1);
        // 这样修改age后，set已经被调用，但是person1中的age值，并没有真正发生变化
        // 因为age的值取决于num， num没有发生改变，age也不会变。
        // 此时，对上面的代码进行修改，把改变后的值赋值给num
        Object.defineProperty(person1, 'age', {
            configurable: true,
            get() {
                console.log('有人读取age属性了，get被调用了');
                return num;
            },
            set(value) {
                console.log('有人修改age属性了，set被调用了, 修改后的值是: ', value);
                num = value;
            }
        })

        // 修改age
        person1.age = 50;
        console.log(person1);
        console.log('num: ', num); // 50

        // 可以看到，修改age后，num的值也跟着改变了，age也成功改变。
        // 这就实现了age和num的值的双向绑定，即修改任何一方的数据，另一方也会跟着变化。
        // 这就是vue数据处理的基础。响应式原理的基础。
    </script>
</body>
</html>